﻿#region Using Statements
using System;
using System.Collections.Generic;
using System.Text;
using GameBase.Screens;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using StarFox.Graphics;
#if DEBUG
using StarFox.Testing;
using System.Diagnostics;
#endif
#endregion

namespace StarFox.ScreenComponents
{
    /// <summary>
    /// Implementation of an asteroid field.
    /// </summary>
    public partial class AsteroidField : ScreenComponent3D
    {
        #region Fields

        StarShip ship;

        #endregion

        #region Sector Data

        // sectors on x-axis - asteroids
        const int NUM_SECTORS_X = 29;
        const int MIDDLE_SECTOR_X = NUM_SECTORS_X / 2;
        const int MIN_SECTOR_X = -MIDDLE_SECTOR_X;
        const int MAX_SECTOR_X = MIDDLE_SECTOR_X;

        // sectors on z-axis - asteroids
        const int NUM_SECTORS_Z = 15;
        const int MIDDLE_SECTOR_Z = 0;
        const int MIN_SECTOR_Z = 0;
        const int MAX_SECTOR_Z = NUM_SECTORS_Z - 1;

        // sector size
        const float SECTOR_SIZE = 100.0f;

        // camera... current, previous sector number
        int LastCamSctPos_X;
        int LastCamSctPos_Z;
        int CurrentCamSctPos_X;
        int CurrentCamSctPos_Z;

        // sector asteroids
        List<IAsteroid>[,] sectorAsteroids = new List<IAsteroid>[NUM_SECTORS_X, NUM_SECTORS_Z];
        List<SmallAsteroid>[,] sectorSmallAsteroids = new List<SmallAsteroid>[NUM_SECTORS_X, NUM_SECTORS_Z];

        // power-up to asteroid proportion
        const int POWER_UP_NUM = 2;

        int numSectors;
        
        /// <summary>
        /// Gets the level distance currently covered.
        /// </summary>
        public float LevelPosition
        {
            get { return MathHelper.Clamp(-ship.Position.Z / (numSectors * SECTOR_SIZE), 0.0f, 1.0f); }
        }

        #endregion

        #region Visibility Data

        const float VIEWABLE_FOV = MathHelper.PiOver4 * 1.35f;
        Vector3[,] sectorDirection = new Vector3[NUM_SECTORS_X, NUM_SECTORS_Z];
        bool[,] sectorIsVisible = new bool[NUM_SECTORS_X, NUM_SECTORS_Z];

        const float MAX_VIEW_DIST = SECTOR_SIZE * (NUM_SECTORS_Z - 1);
        const float FADE_OUT_DIST = MAX_VIEW_DIST * 0.75f;
        const float LOW_DETAIL_DIST = MAX_VIEW_DIST * 0.50f;

        #endregion

        #region Asteroid Data

        List<Model> asteroidModels = new List<Model>();
        List<Model> asteroidModelsLow = new List<Model>();
        List<BoundingSphere> asteroidBSphere = new List<BoundingSphere>();
        List<Model> smallAsteroidModels = new List<Model>();
        List<Model> powerUpModels = new List<Model>();
        List<Model> powerUpModelsLow = new List<Model>();
        List<BoundingSphere> powerUpBSphere = new List<BoundingSphere>();

        #endregion

        #region Graphics

        // effects
        EffectProcessor asteroidEffect;
        EffectProcessor powerUpEffect;

        // light direction for asteroids
        readonly TimeSpan ASTEROID_LIGHT_TIME = TimeSpan.FromSeconds(10);
        float asteroidLightAngle;
        bool asteroidLight_CW;

        // power-up glow
        SpriteBatch SpriteBatch;
        Texture2D glowTexture;
        Rectangle glowRectangle;

        #endregion

        #region Testing

#if DEBUG
        Model boundingSphereModel;
        TestInput input;
        bool showInfo;
#endif

        #endregion

        #region Initialization

        /// <summary>
        /// Initializes a new instance of this class.
        /// <param name="screen">The Screen that this ScreenComponent3D is associated to.</param>
        /// <param name="ship">The StarShip that is associated to this AsteroidField.</param>
        /// <param name="numSectors">The level length expressed in sectors.</param>
        /// </summary>
        public AsteroidField(Screen screen, StarShip ship, int numSectors)
            : base(screen)
        {
            this.ship = ship;
            this.numSectors = numSectors;

            #region Asset Loading

            // asteroids - high detail
            asteroidModels.Add(LoadModel("ASTEROID_01_HIGH"));
            asteroidModels.Add(LoadModel("ASTEROID_02_HIGH"));
            asteroidModels.Add(LoadModel("ASTEROID_03_HIGH"));

            // asteroids - low detail
            asteroidModelsLow.Add(LoadModel("ASTEROID_01_LOW"));
            asteroidModelsLow.Add(LoadModel("ASTEROID_02_LOW"));
            asteroidModelsLow.Add(LoadModel("ASTEROID_03_LOW"));

            // asteroid bounding spheres
            asteroidBSphere.Add(CreateBoundingSphere(asteroidModels[0]));
            asteroidBSphere.Add(CreateBoundingSphere(asteroidModels[1]));
            asteroidBSphere.Add(CreateBoundingSphere(asteroidModels[2]));

            // small asteroids
            smallAsteroidModels.Add(LoadModel("SMALL_ASTEROID_01"));
            smallAsteroidModels.Add(LoadModel("SMALL_ASTEROID_02"));
            smallAsteroidModels.Add(LoadModel("SMALL_ASTEROID_03"));

            // power-ups - hight detail
            powerUpModels.Add(LoadModel("HEALTH_HIGH"));
            powerUpModels.Add(LoadModel("BOOST_HIGH"));
            powerUpModels.Add(LoadModel("FUEL_HIGH"));
            powerUpModels.Add(LoadModel("BOMB_HIGH"));
            powerUpModels.Add(LoadModel("LIFE_HIGH"));

            // power-ups - low detail
            powerUpModelsLow.Add(LoadModel("HEALTH_LOW"));
            powerUpModelsLow.Add(LoadModel("BOOST_LOW"));
            powerUpModelsLow.Add(LoadModel("FUEL_LOW"));
            powerUpModelsLow.Add(LoadModel("BOMB_LOW"));
            powerUpModelsLow.Add(LoadModel("LIFE_LOW"));

            // power-up bounding spheres
            powerUpBSphere.Add(CreateBoundingSphere(powerUpModels[0]));
            powerUpBSphere.Add(CreateBoundingSphere(powerUpModels[1]));
            powerUpBSphere.Add(CreateBoundingSphere(powerUpModels[2]));
            powerUpBSphere.Add(CreateBoundingSphere(powerUpModels[3]));
            powerUpBSphere.Add(CreateBoundingSphere(powerUpModels[4]));

            // power-up glow
            glowTexture = Screen.Content.Load<Texture2D>(ContentSettings.GetAsset("POWER_UP_GLOW"));
            glowRectangle = Rectangle.Empty;
            glowRectangle.Width = (int)(glowTexture.Width * Screen.Scale);
            glowRectangle.Height = (int)(glowTexture.Height * Screen.Scale);

            #endregion

            #region Graphics

            SpriteBatch = Screen.SpriteBatch;

            asteroidEffect = new EffectProcessor();
            asteroidEffect.AmbientLightColor = (new Color(0.25f, 0.25f, 0.25f)).ToVector3();
            asteroidEffect.DirectionalLight0.DiffuseColor = (Color.White).ToVector3();
            asteroidEffect.DirectionalLight0.SpecularColor = (Color.White).ToVector3();

            powerUpEffect = new EffectProcessor();
            powerUpEffect.AmbientLightColor = (new Color(0.25f, 0.25f, 0.25f)).ToVector3();
            // key light
            powerUpEffect.DirectionalLight0.Direction = new Vector3(-0.5265408f, -0.5735765f, -0.6275069f);
            powerUpEffect.DirectionalLight0.DiffuseColor = new Vector3(1, 0.9607844f, 0.8078432f);
            powerUpEffect.DirectionalLight0.SpecularColor = new Vector3(1, 0.9607844f, 0.8078432f);
            // fill light
            powerUpEffect.DirectionalLight0.Direction = new Vector3(0.7198464f, 0.3420201f, 0.6040227f);
            powerUpEffect.DirectionalLight0.DiffuseColor = new Vector3(0.9647059f, 0.7607844f, 0.4078432f);
            powerUpEffect.DirectionalLight0.SpecularColor = Vector3.Zero;
            // back light
            powerUpEffect.DirectionalLight0.Direction = new Vector3(0.4545195f, -0.7660444f, 0.4545195f);
            powerUpEffect.DirectionalLight0.DiffuseColor = new Vector3(0.3231373f, 0.3607844f, 0.3937255f);
            powerUpEffect.DirectionalLight0.SpecularColor = new Vector3(0.3231373f, 0.3607844f, 0.3937255f);

            #endregion

            #region Testing

#if DEBUG
            boundingSphereModel = Screen.Content.Load<Model>(ContentSettings.GetAsset("BOUNDING_SPHERE"));

            input = Game.Services.GetService(typeof(TestInput)) as TestInput;
            if (input == null)
                throw new InvalidOperationException("Service of type TestInput not found");
#endif

            #endregion

            InitializeSectorAsteroids();
            InitializeSectorVisibility();
        }

        #region InitializeSectorAsteroids

        private void InitializeSectorAsteroids()
        {
            for (int z = MAX_SECTOR_Z; z >= MIN_SECTOR_Z; z--)
            {
                for (int x = MIN_SECTOR_X; x <= MAX_SECTOR_X; x++)
                {
                    // coordinate to index
                    int ix = x + MIDDLE_SECTOR_X;
                    int iz = z + MIDDLE_SECTOR_Z;

                    // asteroids
                    sectorAsteroids[ix, iz] = new List<IAsteroid>();
                    GenerateAsteroidSector(sectorAsteroids[ix, iz], x, z);

                    // small asteroids
                    sectorSmallAsteroids[ix, iz] = new List<SmallAsteroid>();
                    GenerateSmallAsteroidSector(sectorSmallAsteroids[ix, iz], x, z);
                }
            }
        }

        #endregion

        #region InitializeSectorVisibility

        private void InitializeSectorVisibility()
        {
            for (int z = MAX_SECTOR_Z; z >= MIN_SECTOR_Z; z--)
            {
                for (int x = MIN_SECTOR_X; x <= MAX_SECTOR_X; x++)
                {
                    // coordinate to index
                    int ix = x + MIDDLE_SECTOR_X;
                    int iz = z;

                    sectorDirection[ix, iz] = new Vector3(x, 0, -z);
                    sectorDirection[ix, iz].Normalize();
                }
            }
        }

        #endregion

        #region LoadModel

        private Model LoadModel(string asset)
        {
            Model model = Screen.Content.Load<Model>(ContentSettings.GetAsset(asset));

            // bone transforms
            Matrix[] modelTransf = new Matrix[model.Bones.Count];
            model.CopyAbsoluteBoneTransformsTo(modelTransf);
            model.Tag = modelTransf;

            return model;
        }

        #endregion

        #region CreateBoundingSphere

        private BoundingSphere CreateBoundingSphere(Model model)
        {
            Matrix[] modelTransf = model.Tag as Matrix[];

            BoundingSphere sphere = new BoundingSphere();
            foreach (ModelMesh mesh in model.Meshes)
            {
                BoundingSphere meshBoundingSphere = mesh.BoundingSphere.Transform(modelTransf[mesh.ParentBone.Index]);
                sphere = BoundingSphere.CreateMerged(sphere, meshBoundingSphere);
            }
            sphere.Center = Vector3.Zero;
            sphere.Radius *= 0.80f;

            return sphere;
        }

        #endregion

        #endregion

        #region GenerateSector

        /// <summary>
        /// Generates the list of IAsteroid objects associated to a specific sector.
        /// </summary>
        /// <param name="list">List of IAsteroid objects.</param>
        /// <param name="x">Sector number on x-axis.</param>
        /// <param name="z">Sector number on z-axis.</param>
        private void GenerateAsteroidSector(List<IAsteroid> list, int x, int z)
        {
            // no asteroids in starting sectors
            if (Math.Abs(x) < 5 && Math.Abs(z) < 5)
                return;

            int numOfAsteroids = StarFoxHelper.RandomInt(2);

            for (int i = 0; i < numOfAsteroids; i++)
            {
                // it's an asteroid
                int model = StarFoxHelper.RandomInt(asteroidModels.Count);

                Vector3 position = new Vector3()
                {
                    X = x * SECTOR_SIZE + StarFoxHelper.RandomFloat(-SECTOR_SIZE * 0.5f, SECTOR_SIZE * 0.5f),
                    Z = -z * SECTOR_SIZE + StarFoxHelper.RandomFloat(-SECTOR_SIZE * 0.5f, SECTOR_SIZE * 0.5f),
                    Y = ship.Position.Y + StarFoxHelper.RandomFloat(-SECTOR_SIZE * 5.0f, SECTOR_SIZE * 5.0f)
                };

                list.Add(new Asteroid(model, position, ship.Position));
            }

            if (StarFoxHelper.RandomInt(100) < POWER_UP_NUM)
            {
                Vector3 position = new Vector3()
                {
                    X = x * SECTOR_SIZE,
                    Z = -z * SECTOR_SIZE,
                    Y = ship.Position.Y + StarFoxHelper.RandomFloat(-SECTOR_SIZE * 1.5f, SECTOR_SIZE * 1.5f)
                };

                float auxRnd = StarFoxHelper.RandomFloat(0, 100);
                if (auxRnd < 25.0f)
                    // boost
                    list.Add(new PowerUp((int)PowerUp.PowerUpType.BOOST, position, ship.Position, PowerUp.PowerUpType.BOOST));
                else if (auxRnd < 50.0f)
                    // fuel
                    list.Add(new PowerUp((int)PowerUp.PowerUpType.FUEL, position, ship.Position, PowerUp.PowerUpType.FUEL));
                else if (auxRnd < 75.0f)
                    // health
                    list.Add(new PowerUp((int)PowerUp.PowerUpType.HEALTH, position, ship.Position, PowerUp.PowerUpType.HEALTH));
                else if (auxRnd < 95.0f)
                    // bomb
                    list.Add(new PowerUp((int)PowerUp.PowerUpType.BOMB, position, ship.Position, PowerUp.PowerUpType.BOMB));
                else
                    // life
                    list.Add(new PowerUp((int)PowerUp.PowerUpType.LIFE, position, ship.Position, PowerUp.PowerUpType.LIFE));
            }
        }

        /// <summary>
        /// Generates the list of SmallAsteroid objects associated to a specific sector.
        /// </summary>
        /// <param name="list">List of SmallAsteroid objects.</param>
        /// <param name="x">Sector number on x-axis.</param>
        /// <param name="z">Sector number on z-axis.</param>
        private void GenerateSmallAsteroidSector(List<SmallAsteroid> list, int x, int z)
        {
            int numOfSmallAsteroids = 1 + StarFoxHelper.RandomInt(1);

            for (int i = 0; i < numOfSmallAsteroids; i++)
            {
                // small asteroid model
                int model = StarFoxHelper.RandomInt(smallAsteroidModels.Count);

                // small asteroid position - xz
                Vector3 position;
                position.X = x * SECTOR_SIZE + StarFoxHelper.RandomFloat(-SECTOR_SIZE * 0.50f, SECTOR_SIZE * 0.50f);
                position.Z = -z * SECTOR_SIZE + StarFoxHelper.RandomFloat(-SECTOR_SIZE * 0.50f, SECTOR_SIZE * 0.50f);

                // small asteroid position - y
                position.Y = ship.Position.Y + StarFoxHelper.RandomFloat(-SECTOR_SIZE * 5.0f, SECTOR_SIZE * 5.0f);

                list.Add(new SmallAsteroid(model, position));
            }
        }

        #endregion

        #region KillNearbyAsteroids

        /// <summary>
        /// Kills all nearby asteroids.
        /// </summary>
        public void KillNearbyAsteroids()
        {
            for (int z = 1; z >= 0; z--)
            {
                for (int x = -1; x <= 1; x++)
                {
                    // coordinate to index
                    int ix = x + MIDDLE_SECTOR_X;
                    int iz = z + MIDDLE_SECTOR_Z;

                    // asteroids
                    sectorAsteroids[ix, iz].Clear();

                    // small asteroids
                    sectorSmallAsteroids[ix, iz].Clear();
                }
            }
        }

        #endregion
    }
}
